//	Ordinaire.Database.DatabaseFactory
//	Copyright (c) 2008 Jonathan Loe
//
//	MIT license (http://en.wikipedia.org/wiki/MIT_License)
//
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights 
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//	of the Software, and to permit persons to whom the Software is furnished to do so, 
//	subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in all 
//	copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
//	PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
//	FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;

namespace Ordinaire.Db
{
	/// <summary>
	/// This class handles the creation of IDatabase class instances.
	/// </summary>
	/// <seealso cref="Ordinaire.DatabaseCollection"/>
	public class DbFactory
	{
		#region Variables
		private const string _OleConnectionStringPattern = "Provider=.*;";

		private static IDatabase _instance;
		private static DbCollection _instances;
		#endregion

		#region Public methods
		#region Default instance
		/// <summary>
		/// Initializes a default instance of IDatabase class with the specified connection string.
		/// </summary>
		/// <param name="connectionString">connection string</param>
		/// <returns>default instance</returns>
		public static IDatabase CreateInstance(string connectionString)
		{
			if (String.IsNullOrEmpty(connectionString)) { throw new ArgumentNullException("connectionString"); }

			instance = GetDatabase(connectionString);
			return instance;
		}

		/// <summary>
		/// Returns default instance of IDatabase. This property will attempt to
		/// create a new instance of IDatabase class using the first connection
		/// string listed in application config file if the instance has not been
		/// initialized using CreateInstance().
		/// </summary>
		/// <remarks>
		/// Note that connection string specified in machine config file 'Machine.config'
		/// takes precedence of those in application config file.
		/// </remarks>
		/// <value>Instance of IDatabase class.</value>
		public static IDatabase Instance
		{
			get
			{
				if (instance == null) { instance = CreateInstance(); }
				return instance;
			}
		}
		#endregion

		#region Collection of instances
		/// <summary>
		/// Creates instances of IDatabase class based on all the connection strings specified
		/// in config file.
		/// </summary>
		public static void CreateInstances()
		{
			foreach (ConnectionStringSettings setting in ConfigurationManager.ConnectionStrings)
			{
				if (String.IsNullOrEmpty(setting.Name))
					throw new ConfigurationErrorsException(ExceptionMessage.EmptyConnectionName);

				if (String.IsNullOrEmpty(setting.ConnectionString))
				{
					throw new ConfigurationErrorsException(
						String.Format(ExceptionMessage.EmptyConnectionStringInConfigFormat, setting.Name)
					);
				}

				CreateInstances(setting.Name, false);
			}
		}

		/// <summary>
		/// Create new instance of IDatabase class with a connection string of the specified name
		/// set inside the config file.
		/// </summary>
		/// <param name="name">connection string name</param>
		/// <returns>The new instance of IDatabase class</returns>
		public static IDatabase CreateInstances(string name)
		{
			if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); }
			return CreateInstances(name, false);
		}

		/// <summary>
		/// Create new instance of IDatabase class with a connection string of the specified name
		/// set inside the config file and whether to set the new instance as the default instance.
		/// </summary>
		/// <param name="name">connection string name</param>
		/// <param name="isDefault">whether to set this instance as default</param>
		/// <returns>The new instance of IDatabase class</returns>
		public static IDatabase CreateInstances(string name, bool isDefault)
		{
			if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); }

			if (ConfigurationManager.ConnectionStrings.Count == 0)
				throw new ConfigurationErrorsException(ExceptionMessage.NoConnectionStringInConfig);

			// Take the first connection string as the default connection string
			ConnectionStringSettings connSettings = ConfigurationManager.ConnectionStrings[name];
			if (connSettings == null)
				throw new ConfigurationErrorsException(String.Format(ExceptionMessage.InvalidConnectionNameInConfigFormat, name));

			// Create default instance
			if (isDefault) { CreateInstance(connSettings.ConnectionString); }

			return CreateInstances(name, connSettings.ConnectionString);
		}

		/// <summary>
		/// Initializes a new instance of IDatabase class with the specified name and connection string.
		/// </summary>
		/// <param name="name">name</param>
		/// <param name="connectionString">connection string</param>
		/// <returns>IDatabase instance</returns>
		public static IDatabase CreateInstances(string name, string connectionString)
		{
			if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); }
			if (String.IsNullOrEmpty(connectionString)) { throw new ArgumentNullException("connectionString"); }

			// Initialize Hashtable
			if ( instances == null ) { instances = new DbCollection(); }

			IDatabase database = GetDatabase(connectionString);
			if ( database != null ) { instances.Add(name, GetDatabase(connectionString)); }

			return database;
		}

		/// <summary>
		/// Returns collections of instances.
		/// </summary>
		public static DbCollection Instances
		{
			get { return instances; }
		}
		#endregion
		#endregion

		#region Private methods
		/// <summary>
		/// Initializes a default instance of IDatabase class using the first connection string listed in application config file.
		/// </summary>
		/// <returns>instance of IDatabase class</returns>
		private static IDatabase CreateInstance()
		{
			if (ConfigurationManager.ConnectionStrings.Count == 0)
				throw new ConfigurationErrorsException(ExceptionMessage.NoConnectionStringInConfig);

			// Take the first connection string as the default connection string
			string connectionString = ConfigurationManager.ConnectionStrings[0].ConnectionString;

			if (String.IsNullOrEmpty(connectionString))
				throw new ConfigurationErrorsException(ExceptionMessage.EmptyConnectionString);

			return CreateInstance(connectionString);
		}

		/// <summary>
		/// Creates the right IDatabase depending on the specified connection string.
		/// </summary>
		/// <param name="connectionString">connection string</param>
		/// <returns>IDatabase instance</returns>
		private static IDatabase GetDatabase(string connectionString)
		{
			IDatabase database = null;

			if (Regex.IsMatch(connectionString, _OleConnectionStringPattern, RegexOptions.IgnoreCase))
				database = new OleDb(connectionString);
			else
				database = new SqlDb(connectionString);

			return database;
		}

		/// <summary>
		/// Returns IDatabase instance. In web application, this property attempts to retrieve the object
		/// from a stored session if exist. Otherwise, new instance will be created and stored in the session.
		/// </summary>
		private static IDatabase instance
		{
			get
			{
				if (HttpContext.Current != null && HttpContext.Current.Session != null)
				{
					object session = HttpContext.Current.Session[SessionName.DATABASE_FACTORY];

					if (session != null) { return (IDatabase)session; }
					// Store existing instance to session if does not exist yet
					if (session == null && _instance != null)
					{
						instance = _instance;
						return instance;
					}

					return null;
				}
				else { return _instance; }
			}
			set
			{
				if (HttpContext.Current != null && HttpContext.Current.Session != null)
				{
					HttpContext.Current.Session.Remove(SessionName.DATABASE_FACTORY);
					HttpContext.Current.Session.Add(SessionName.DATABASE_FACTORY, value);
				}
				else { _instance = value; }
			}
		}

		/// <summary>
		/// Return DatabaseCollection instance. In web application, this property attempts to retrieve the object
		/// from a stored session if exist. Otherwise, new instance will be created and stored in the session.
		/// </summary>
		private static DbCollection instances
		{
			get
			{
				if (HttpContext.Current != null && HttpContext.Current.Session != null)
				{
					object session = HttpContext.Current.Session[SessionName.DATABASE_FACTORIES];

					if (session != null) { return (DbCollection)session; }
					// Store existing instance to session if does not exist yet
					if (session == null && _instances != null)
					{
						instances = _instances;
						return instances;
					}

					return null;
				}
				else { return _instances; }
			}
			set
			{
				if (HttpContext.Current != null && HttpContext.Current.Session != null)
				{
					HttpContext.Current.Session.Remove(SessionName.DATABASE_FACTORIES);
					HttpContext.Current.Session.Add(SessionName.DATABASE_FACTORIES, value);
				}
				else { _instances = value; }
			}
		}
		#endregion
	}
}
